在做畫面之前,通常都先把這次的商業邏輯處理好放在 Context 裡面,到時候實作畫面互動的時候直接互叫我們做好的 Context 函式即可,這個章節的目標是完成功能所需要的函式,並可以在 iex 裡面操作整個流程。
在這次的示範專案 Gratitude,我們要做的是感激筆記,其實結構上比較像是短的部落格或是留言板,需要儲存的格式相對簡單。
首先我們先決定資料的樣子,目前我們唯一需要的欄位只有內容而已,另外 Phoenix 會幫我們加上 id、inserted_at、updated_at 這三個欄位。
Elixir 用來操作資料庫的 Ecto 內建在 Phoenix 裡面,這個套件提供了我們產生 Schema 的指令,我們先執行空的看看他會給什麼建議
mix phx.gen.schema
** (Mix) Invalid arguments
mix phx.gen.schema expects both a module name and
the plural of the generated resource followed by
any number of attributes:
mix phx.gen.schema Blog.Post blog_posts title:string
在 mix phx.gen.schema
後面加上我們要的 Schema module 名稱,資料庫的表格名,以及我們要的欄位。
雖然 Schema module 的名稱可以隨便取,但為了之後方便好找,絕大多數的時候我們都使用 Context名.Schema名
這種格式,所以我們這次 Context 叫 Notes
的 Schema 叫 Note
。指令就變成:
mix phx.gen.schema Notes.Note notes content:string
執行後:
* creating lib/gratitude/notes/note.ex
* creating priv/repo/migrations/20230922133805_create_notes.exs
Remember to update your repository by running migrations:
$ mix ecto.migrate
我們多了兩個檔案,第一個是我們的 Schema module lib/gratitude/notes/note.ex
,
第二個是用來建立資料庫表格的 migration 檔案 priv/repo/migrations/20230922133805_create_notes.exs
。
我們先看看 Schema module,他主要分為兩個區塊:
schema
macroschema "notes" do
field :content, :string
timestamps()
end
這個區塊用來定義資料庫取出的資料對應到 Elixir 的型別,這邊我們只有一個欄位,所以只有一行 field :content, :string
,如果有多個欄位,就會有多行 field
。
另一個 timestamps()
函式則為我們加上 inserted_at
跟 updated_at
這兩個欄位。
changeset
函式def changeset(note, attrs) do
note
|> cast(attrs, [:content])
|> validate_required([:content])
end
這個是當我們要儲存資料之前會使用的函式,首先會執行 cast/2
先把被輸入的對象與輸入的資料轉成 Changeset(改變集),再使用 validate_ 開頭的檢查函式來確認資料是否符合我們的規範,目前預設的給的是檢查 :content
是否存在。
接下來我們來看看 migration 檔案 priv/repo/migrations/20230922133805_create_notes.exs
def change do
create table(:notes) do
add :content, :string
timestamps()
end
end
可以看到這邊的 change 函式,在我們執行 資料庫 migration 的時候如果這個 migration 檔案還沒有執行過的話就會被執行, 裡面的則是這次要執行的內容。
這邊產生的函式都算是直覺,建立一個名為 notes
的資料表,裡面有一個 content
欄位,還有跟剛剛在 schema 中的類似會幫我們增加 inserted_at
跟 updated_at
這兩個欄位的 timestamps()
(不是同一個,這邊的 Ecto.Migration.timestamps/1
是幫我們加欄位,剛剛的是 Ecto.Schema.timestamps/1
幫我們定義型態,不過現在不用太在意他們的細節)。
我們現在已經有了 Schema 跟 migration 檔案,接下來就是要執行 migration 了,執行 migration 的指令是:
mix ecto.migrate
執行後會看到:
Compiling 1 file (.ex)
Generated gratitude app
23:01:23.264 [info] == Running 20230922133805 Gratitude.Repo.Migrations.CreateNotes.change/0 forward
23:01:23.265 [info] create table notes
23:01:23.266 [info] == Migrated 20230922133805 in 0.0s
現在我們成功地在資料庫建立了我們需要的表格與欄位,接下來我們來直接使用 Ecto 來操作看看。
執行 iex -S mix
進入這個專案的 iex :
撈出所有的 Note
Gratitude.Repo.all(Gratitude.Notes.Note)
#=> []
我們什麼都沒有拿到,因為我們還沒有任何資料,但我們這個時候也發現,Gratitude.Repo
與 Gratitude.Notes.Note
實在是很攏長,還好我們可以在要使用他們之前先 alias。
alias Gratitude.Repo
alias Gratitude.Notes.Note
Repo.all(Note)
剛剛使用的 Gratitude.Repo 是 Ecto 提供給我們專案的 Module,裡面實作了所有 Ecto.Repo 這個 behaviour 規定用來操作資料庫的函式,一樣都可以在 Ecto.Repo 文件中找到
(behaviour 不在此次範圍,可以參考文件,但是現在先不用太在意,只需要知道 Gratitude.Repo 的方法可以在 Ecto.Repo 文件 找到即可)
我們先使用 insert/2 來新增幾筆資料:
Repo.insert(%Note{content: "真的很感謝今天有記得帶傘"})
#=>
{:ok,
%Gratitude.Notes.Note{
__meta__: #Ecto.Schema.Metadata<:loaded, "notes">,
id: 1,
content: "真的很感謝今天有記得帶傘",
inserted_at: ~N[2023-09-22 14:11:21],
updated_at: ~N[2023-09-22 14:11:21]
}}
可以看到回傳的結果是典型的成功 tuple,第一個元素是 :ok
,第二個元素則是我們新增的資料,裡面有他在資料庫的 id 與其他欄位。
再新增幾個
Repo.insert(%Note{content: "剛剛有人幫我按著電梯門"})
Repo.insert(%Note{content: "今天便當的炸蝦好好吃"})
這個時候我們再查詢一次全部的 Note
Repo.all(Note)
就會看到我們剛剛輸入的項目了
下一篇我們會在 Context 裡面把這些操作包裝起來,讓我們可以容易的在其他地方使用。